package com.portal.service.impl;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.portal.config.pay.AlipayConfig;
import com.portal.constant.MQConst;
import com.portal.constant.RedisConst;
import com.portal.enums.OrderStatus;
import com.portal.exception.Assert;
import com.portal.mapper.AddressMapper;
import com.portal.mapper.BookMapper;
import com.portal.mapper.ItemMapper;
import com.portal.mapper.OrderMapper;
import com.portal.model.entity.Address;
import com.portal.model.entity.Book;
import com.portal.model.entity.Item;
import com.portal.model.entity.Order;
import com.portal.model.vo.CartItemVo;
import com.portal.model.vo.CartVo;
import com.portal.model.vo.OrderItemVo;
import com.portal.model.vo.OrderVo;
import com.portal.result.impl.BusinessCode;
import com.portal.service.CartService;
import com.portal.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Autowired
    private CartService cartService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ItemMapper itemMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private AddressMapper addressMapper;
    @Autowired
    private BookMapper bookMapper;
    @Autowired
    private AlipayClient alipayClient;
    @Autowired
    private AlipayConfig alipayConfig;
    @Autowired
    private RedissonClient redissonClient;


    /**
     * 下单：
     * 1、库存，数据预热：数据库的库存加载到redis中hash类型存储； 下单是预扣减库存，支付后才改db
     * 2、生成订单解决了幂等问题
     * 3、库存解决了超卖问题
     * 4、下单要清空购物车，mq异步清空
     * 5、解决超时未支付订单自动取消
     */
    @Transactional
    @Override
    public String createOrder(Long userId, Integer addressId) {

        // 通过hutool工具包生成雪花id，作为订单id
        long orderId = IdUtil.getSnowflake(1, 1).nextId();

        //1. 获取购物车对象
        CartVo cartVo = cartService.getCart(userId);
        //2. 遍历购物项集合
        for (CartItemVo cartItem : cartVo.getItemList()) {
            // 同步锁
            //synchronized (this) {
            //    // 获取Redis中库存数量
            //    Integer storeCount = (Integer) redisTemplate.opsForHash().get(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString());
            //    // 判断：如果库存量小于购买量，返回库存不足
            //    Assert.error(storeCount < cartItem.getQuantity(), BusinessCode.STORAGE_NOT_ENOUGH);
            //    // 否者：减库存
            //    redisTemplate.opsForHash().increment(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString(), -cartItem.getQuantity());
            //    log.info("书籍id:{}, 书籍库存:{}", cartItem.getBookId(), storeCount);
            //}

            // 方案2：redis的setnx 分布式锁
            // =================================执行业务=================================
            //Boolean isLock = false;  //锁的状态
            //try {
            //    //1.获取锁，获取到锁返回true
            //    isLock = redisTemplate.opsForValue().setIfAbsent(
            //            RedisConst.LOCK_PREFIX + ":" + cartItem.getBookId(), 1, 20, TimeUnit.SECONDS);
            //    //2. 判断：如果获取锁失败，直接返回错误
            //    if (!isLock) {
            //        log.info("获取锁失败");
            //        Assert.error(!isLock, BusinessCode.GET_LOCK_FAIL);
            //    }
            //
            //    // =================================执行业务=================================
            //    Integer storeCount = (Integer) redisTemplate.opsForHash().get(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString());
            //    // 判断：如果库存量小于购买量，返回库存不足
            //    Assert.error(storeCount < cartItem.getQuantity(), BusinessCode.STORAGE_NOT_ENOUGH);//25
            //    // 否者：减库存
            //    redisTemplate.opsForHash().increment(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString(), -cartItem.getQuantity());
            //    log.info("书籍id:{}, 书籍库存:{}", cartItem.getBookId(), storeCount);
            //} finally {
            //    if (isLock) {
            //        redisTemplate.delete(RedisConst.LOCK_PREFIX + ":" + cartItem.getBookId()); // 执行完业务，释放锁
            //    }
            //}
            // 解决：1、锁的互斥； 2、锁的优雅释放；ttl/finally； 3、锁的自旋（没有获取到锁，要有重试，还要有重试次数的限制，因为不能一直去重试）
            // 4、锁的自动续期：业务没有执行完，锁背释放了，这样不行； 5、加锁解锁的归一性； 6、支持可重入

            // 方案3： Redisson
            //使用Redisson实现分布式锁，解决超卖问题
            RLock lock = redissonClient.getLock(RedisConst.LOCK_PREFIX + ":" + cartItem.getBookId());
            try {
                // 获取锁，如果没有获取到会自旋获取锁
                lock.lock();

                try {
                    TimeUnit.SECONDS.sleep(25);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                // =================================执行业务=================================
                Integer storeCount = (Integer) redisTemplate.opsForHash().get(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString());
                // 判断：如果库存量小于购买量，返回库存不足
                Assert.error(storeCount < cartItem.getQuantity(), BusinessCode.STORAGE_NOT_ENOUGH);//25
                // 否者：减库存
                redisTemplate.opsForHash().increment(RedisConst.BOOK_STORAGE, cartItem.getBookId().toString(), -cartItem.getQuantity());
                log.info("书籍id:{}, 书籍库存:{}", cartItem.getBookId(), storeCount);
            } finally {
                //持有锁才释放锁
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    log.info("释放锁: {}", lock);
                }
            }


            // 保存订单项
            Item item = new Item();
            item.setBookId(cartItem.getBookId());
            item.setBookName(cartItem.getName());
            item.setPrice(cartItem.getPrice());
            item.setBuyCount(cartItem.getQuantity());
            item.setSumPrice(cartItem.getSum());
            item.setOrderId(orderId);
            item.setState(OrderStatus.NO_PAY.getCode());

            itemMapper.insert(item);
        }

        // 保存订单
        Order order = new Order();
        order.setId(orderId);
        // 订单编号： ORDER_BOOK_ + 订单id
        order.setOrderNum("ORDER_BOOK_" + orderId);
        order.setTotalPrice(cartVo.getTotalPrice());
        order.setUserId(userId);
        order.setAddressId(addressId);
        order.setState(OrderStatus.NO_PAY.getCode());
        orderMapper.insert(order);

        // 异步删除购物车
        rabbitTemplate.convertAndSend(MQConst.CLEAR_CART_QUEUE,cartVo);

        // 发送MQ消息，解决超时未支付的问题
        rabbitTemplate.convertAndSend(MQConst.ORDER_EXCHANGE,MQConst.ORDER_KEY_NORMAL,order);

        return order.getOrderNum();
    }

    @Override
    public List<OrderVo> queryOrder(Long userId, Integer status) {
        // 根据用户id + 状态查询订单，按照时间降序
        LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery(Order.class);
        wrapper.eq(Order::getUserId, userId);
        wrapper.eq(status != null && status != 0, Order::getState, status);
        // 降序
        wrapper.orderByDesc(Order::getCreateTime);
        // 查询
        List<Order> orderList = list(wrapper);
        // 转换
        List<OrderVo> orderVoList = orderList.stream().map(order -> {
            OrderVo orderVo = BeanUtil.copyProperties(order, OrderVo.class);
            orderVo.setStatus(order.getState());
            orderVo.setOrderStatus(OrderStatus.matchCode(order.getState()).getDescribe());
            // 查询地址
            Address address = addressMapper.selectById(order.getAddressId());
            orderVo.setAddress(address.getProvince()+ address.getCity()+address.getArea()+address.getDetailAddress());
            orderVo.setPhone(address.getTelephone());
            orderVo.setReceiver(address.getReceiver());
            // 设置用户id
            orderVo.setUserId(order.getUserId().toString());
            orderVo.setId(order.getId().toString());

            // 查询订单项
            List<Item> itemList = itemMapper.selectList(
                    Wrappers.lambdaQuery(Item.class).eq(Item::getOrderId, order.getId()));
            List<OrderItemVo> itemVoList = itemList.stream().map(item -> {
                OrderItemVo orderItemVo = BeanUtil.copyProperties(item, OrderItemVo.class);
                // 根据图书id查询
                Book book = bookMapper.selectById(item.getBookId());
                orderItemVo.setId(item.getId());
                orderItemVo.setBookName(book.getName());
                orderItemVo.setBookImg(book.getImgSrc());
                orderItemVo.setBuyCount(item.getBuyCount());
                orderItemVo.setSumPrice(item.getSumPrice());
                return orderItemVo;
            }).collect(Collectors.toList());
            // 设置订单项
            orderVo.setOrderItem(itemVoList);
            return orderVo;
        }).collect(Collectors.toList());
        return orderVoList;
    }

    // 提交支付请求
    @Override
    public String pay(Long orderId) {
        //通过id查询订单
        Order order = getById(orderId);

        //创建交易页面的支付请求对象
        AlipayTradePagePayRequest request = getAlipayTradePagePayRequest(order);

        String form;
        try {
            //调用SDK生成表单
            form = alipayClient.pageExecute(request).getBody();
            log.info("支付宝支付表单：" + form);
        } catch (AlipayApiException e) {
            e.printStackTrace();
            //让事务回滚
            throw new RuntimeException(e.getMessage());
        }

        //返回表单给控制器
        return form;
    }


    /**
     * 创建交易页面的支付请求对象
     */
    private AlipayTradePagePayRequest getAlipayTradePagePayRequest(Order order) {
        //1.准备支付的参数，包括需要支付的订单的id，价格，物品名称
        AlipayTradePagePayModel model = new AlipayTradePagePayModel();
        model.setOutTradeNo(order.getId().toString()); // 需要支付的订单id，自定义的订单id，不能重复，唯一，已经支付的无法继续支付
        model.setTotalAmount(order.getTotalPrice().toString()); // 需要支付的钱 model.setTotalAmount("88.88");
        model.setSubject("书籍"); // 要支付的物品，比如 model.setSubject("Iphone6 16G");
        model.setProductCode("FAST_INSTANT_TRADE_PAY");  //销售产品码，电脑支付场景就使用FAST_INSTANT_TRADE_PAY

        // 2.创建支付的请求，设置上面的准备支付的参数
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setBizModel(model);

        // 3.设置支付宝异步回调，这个需要用内网穿透
        request.setNotifyUrl(alipayConfig.getNotifyUrl());

        // 4.设置支付宝支付成功返回的页面，这里让支付成功直接回到static下的一个静态页面中
        request.setReturnUrl(alipayConfig.getReturnUrl());
        return request;
    }

    @Override
    public void updateOrderStatus(Long orderId, Integer status) {
        // 1、修改订单状态为： NO_SEND(2, "待发货")
        update(Wrappers.lambdaUpdate(Order.class)
                .eq(Order::getId, orderId).set(Order::getState, status));

        // 2、修改订单明细的状态为： NO_SEND(2, "待发货")
        // 查询订单下所有的item明细
        List<Item> itemList =
                itemMapper.selectList(Wrappers.lambdaQuery(Item.class).eq(Item::getOrderId, orderId));
        itemList.forEach(item -> {
            // 修改订单明细的状态为： NO_SEND(2, "待发货")
            itemMapper.update(Wrappers.lambdaUpdate(Item.class)
                    .eq(Item::getId, item.getId()).set(Item::getState, status));
            // 更新库存
            this.updateBookStorage(item);
        });
    }

    @Override
    public void updateBookStorage(Item item) {
        // 3、修改book表数据库库存
        Book book = bookMapper.selectById(item.getBookId());
        // 设置库存、购买量
        book.setStoreCount(book.getStoreCount() - item.getBuyCount());
        book.setBuyCount(book.getBuyCount() + item.getBuyCount());
        bookMapper.updateById(book);
    }
}



















